package com.simple.trade.service.vm.impl;

import cn.hutool.core.util.NumberUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.*;
import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
import com.simple.common.entity.ResData;
import com.simple.common.entity.constant.Payment;
import com.simple.common.entity.dto.pay.*;
import com.simple.common.entity.po.sys.Application;
import com.simple.common.entity.po.sys.ChannelComp;
import com.simple.common.entity.po.sys.Product;
import com.simple.common.entity.po.sys.Trade;
import com.simple.ds.service.ChannelService;
import com.simple.ds.service.TradeApi;
import com.simple.trade.holder.PayContext;
import com.simple.trade.service.vm.RedirectService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.time.Clock;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author yasud
 * @date 2020年10月13日 09:21
 * @description
 */
@Slf4j
@Service
public class RedirectServiceImpl implements RedirectService {

    @Autowired
    ChannelService channelService;

    @Autowired
    TradeApi tradeApi;

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    Clock clock;

    /**
     * 动态跳转
     *
     * @param channelFlag
     * @param flag
     * @param param
     * @param header
     * @return
     */
    @Override
    @SneakyThrows
    public String redirectDynamic(String channelFlag,
                                  String flag,
                                  Map param,
                                  Map header) {
        ChannelComp channel = channelService.findChannelCompByFlag(channelFlag);
        Verify.verifyNotNull(channel, "渠道%s无效请检查配置是否有误", channelFlag);
        RedirectDto dto = RedirectDto.generateRedirectDto(channel, PayContext.current().getContext(), null, param, header, flag);
        ResData<RedirectRspDto> dtoResData = forwardToChannel(dto, channel, Payment.REDIRECT_DYNAMIC_CODE, RedirectRspDto.class);
        if (dtoResData.isOk()) {
            RedirectRspDto rspDto = dtoResData.getData();
            log.info("交易id{}跳转成功返回{}", rspDto.getTradeNo(), objectMapper.writeValueAsString(rspDto));
            Trade trade = null;
            Product product = null;
            if (Objects.nonNull(rspDto.getTradeNo())) {
                trade = tradeApi.findById_(rspDto.getTradeNo());
                Verify.verifyNotNull(trade, "交易%s找不到请检查字段是否正确", rspDto.getTradeNo());
                PayContext.current().iam(trade);
                channel = PayContext.current().channel();
                product = PayContext.current().product();
            }

            RedirectFinalDto redirectFinalDto = RedirectFinalDto.generateRedirectDto(channel, PayContext.current().getContext(), product, param, header, flag, trade);
            ResData<RedirectFinalRspDto> finalRspDtoResData = forwardToChannel(redirectFinalDto, channel, Payment.REDIRECT_DYNAMIC_FINAL_CODE, RedirectFinalRspDto.class);

            RedirectFinalRspDto finalRspDto = finalRspDtoResData.getData();
            if (finalRspDtoResData.isOk()) {
                if (StringUtils.isNotBlank(finalRspDto.getRedirectUrl())) {
                    return processExtraParam(finalRspDto.getRedirectUrl(), finalRspDto.getTrade(), PayContext.current().app());
                }
                Verify.verifyNotNull(finalRspDto.getTrade(), "在未返回redirectUrl的情况下必须返回交易");
                tradeApi.updateById_(finalRspDto.getTrade());
                //一般来说返回的trade都是从渠道服务返回的交易号去查询 如果自己直接返回那么就会出问题
                Verify.verifyNotNull(PayContext.current().app(), "交易%s无效请使用正确的交易进行处理", finalRspDto.getTrade().getId());
                return processExtraParam(finalRspDto.getTrade().getSyncUrl(), finalRspDto.getTrade(), PayContext.current().app());
            } else {
                log.info("最终跳转失败,原因{}", finalRspDtoResData.getMsg());
                Verify.verify(false, "跳转失败,原因%s", finalRspDtoResData.getMsg());
                return null;
            }
        } else {
            log.info("跳转失败,原因{}", dtoResData.getMsg());
            Verify.verify(false, "跳转失败,原因%s", dtoResData.getMsg());
            return null;
        }
    }

    /**
     * 增加附带参数
     *
     * @param redirectUrl
     * @param trade
     * @return
     */
    private String processExtraParam(String redirectUrl, Trade trade, Application app) {
        if (StringUtils.isBlank(redirectUrl)) return null;
        URI uri = null;
        try {
            uri = URI.create(redirectUrl);
        } catch (Exception ex) {
            ex.printStackTrace();
            Verify.verify(false, "返回跳转地址%s无效!", redirectUrl);
        }

        String query = uri.getQuery();

        long timestamp = clock.millis();
        Map<String, String> map = Maps.newHashMap();
        if (StringUtils.isNotBlank(query)) {
            map = Splitter.on("&").trimResults().omitEmptyStrings().withKeyValueSeparator("=").split(query);
        }

        Map<String, Object> param = Maps.newTreeMap();
        param.putAll(map);
        param.put("orderNo", trade.getMerchantOrderNo());
        param.put("status", trade.getStatus());
        param.put("sign", sign(param, timestamp, app.getSecretKey()));

        String join = Joiner.on("&").withKeyValueSeparator("=").join(param);
        return uri.getScheme() + "://" + uri.getHost() +  uri.getPath() + "?" + join;
    }

    /**
     * 生成签名
     *
     * @param key
     * @param param
     * @param timestamp
     * @return
     */
    private String sign(Map<String, Object> param, Long timestamp, String key) {
        List<Map.Entry<String, Object>> collect = param.entrySet().stream().filter(stringObjectEntry -> Objects.nonNull(stringObjectEntry.getValue())).sorted(Comparator.comparing(stringObjectEntry -> stringObjectEntry.getKey())).collect(Collectors.toList());
        String str = Joiner.on("&").withKeyValueSeparator("=").join(collect);
        return DigestUtils.sha256Hex(str + timestamp + key);
    }


    /**
     * @param channelFlag
     * @param flag
     * @param param
     * @param header
     * @return
     */
    @Override
    public Pair<MediaType, Object> infiltration(String channelFlag,
                                                String flag,
                                                Map param,
                                                Map header) {
        String tradeNo = (String) param.getOrDefault("tradeNo", "");
        Preconditions.checkState(NumberUtil.isLong(tradeNo), "交易id无效");
        Trade trade = tradeApi.findById_(Longs.tryParse(tradeNo));
        Verify.verifyNotNull(trade, "交易id无效");
        PayContext.current().iam(trade);
        ChannelComp channel = PayContext.current().channel();
        Product product = PayContext.current().product();
        InfiltrationDto infiltrationDto = InfiltrationDto.generateRedirectDto(channel, PayContext.current().getContext(), product, param, header, flag, trade);
        ResData<InfiltrationRspDto> rspDtoResData = forwardToChannel(infiltrationDto, channel, Payment.REDIRECT_INFILTRATION_CODE, InfiltrationRspDto.class);
        if (rspDtoResData.isOk()) {
            InfiltrationRspDto rspDto = rspDtoResData.getData();
            tradeApi.updateById_(rspDto.getTrade());
            return Pair.of(MediaType.valueOf(rspDto.getMediaType()), rspDto.getBody());
        } else {
            log.info("跳转失败,原因{}", rspDtoResData.getMsg());
            Verify.verify(false, "跳转失败,原因%s", rspDtoResData.getMsg());
            return null;
        }
    }

    @Autowired
    LoadBalancerClient loadBalancerClient;

    @Autowired
    RestTemplate restTemplate;

    /**
     * 这里用rest方式最简单，
     * 内部接口相互通信全部使用post
     *
     * @param directPayDto
     * @param channel
     */
    @SneakyThrows
    public <R> ResData<R> forwardToChannel(BaseDto directPayDto, ChannelComp channel, String type, Class<R> responseClass) {
        log.info("请求渠道{}开始参数为{}", channel.getCode(), objectMapper.writeValueAsString(directPayDto));
        ServiceInstance instance = loadBalancerClient.choose(channel.getServiceProvider());
        Preconditions.checkNotNull(instance, "The service is starting. Please try again later");
        ResData resData = Suppliers.memoize(() -> {
            try {
                ResponseEntity<String> responseEntity = restTemplate.postForEntity(getServiceUrl(instance, type), directPayDto, String.class);
                ResData rResData = objectMapper.readValue(responseEntity.getBody(), ResData.class);
                return rResData;
            } catch (Exception ex) {
                ex.printStackTrace();
                return ResData.error(Throwables.getRootCause(ex).getMessage());
            }
        }).get();
        R r = objectMapper.readValue(objectMapper.writeValueAsString(resData.getData()), responseClass);
        resData.setData(r);
        log.info("请求渠道{}结束返回{}", channel.getCode(), objectMapper.writeValueAsString(resData));
        return resData;
    }

    private String getServiceUrl(ServiceInstance instance, String type) {
        return "http://" + instance.getServiceId() + "/" + type;
    }
}
